import requests
from bs4 import BeautifulSoup
import logging
import pandas as pd
import hashlib
import time

class MusicScraper:
    def __init__(self, singer, appid, url, secret):
        self.singer = singer
        self.appid = appid
        self.url = url
        self.headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36"
        }
        self.secret = secret

    def fetch_music_info(self):
        
        try:
            response = requests.get(self.url, headers=self.headers)
            if response.history != []:   # history 会记录重定向过程的信息
                logging.info(f'Sites redirected: {response.history}')
            if response.status_code != 200:
                logging.error(
                    f'Requests Error: {response.status_code}, {response.reason}')
            response.raise_for_status()
        except requests.exceptions.RequestException as e:
            logging.error(f"Error in fetching music info: {e}")
            return None

        soup = BeautifulSoup(response.text, 'html.parser')
        song_box = soup.find_all(name='div', class_='song-box', attrs={'data-v-e9211496': True})
        singer_box = soup.find_all(name='div', class_='artist ellipsis')

        music_info = []
        for song, singer_list in zip(song_box, singer_box):
            musicname = song.find('a').text
            song_id = song.find('a').get('href')
            musicid = "https://music.91q.com" + song_id
            T_idx = song_id.find('T')
            TSID = song_id[T_idx:]

            i = singer_list.find_all(name='a')
            singer = ", ".join(a.get_text(strip=True) for a in i)
            music_info.append(
                {'musicname': musicname, 'musicid': musicid, 'singer': singer, 'TSID': TSID})
            # print(music_info)
        return pd.DataFrame(music_info)
    def _gen_timestamp(self):
        """
        生成当前时间戳
        :return: 整数型时间戳（10位整数）
        """
        return int(time.time())

    def _create_sign(self,TSID):
        """
        :param TSID: 歌曲标识符
        :param timestamp: 时间戳
        :return: 签名字符串
        """
        # r='TSID=T10063954909,T10061819807,T10059607241,T10064980207,T10064970806&appid=16073360&timestamp=17421293440b50b02fd0d73a9c4c8c3a781c30845f'
        r = f'TSID={TSID}&appid={self.appid}&timestamp={self._gen_timestamp()}{self.secret}'
        return hashlib.md5(r.encode('utf-8')).hexdigest()
        # 首先， r.encode('utf-8') 将字符串 r 转换为字节码
        # 然后， hashlib.md5() 对这些字节码进行 MD5 加密，生成一个哈希对象
        # 最后， hexdigest() 将这个哈希对象转换为十六进制字符串 (源代码的使用需求)

    def _request_tracklink(self, url, params):
        """
        发送 HTTP GET 请求并处理常见异常
        :param url: 请求地址
        :param params: 请求参数
        :return: 响应对象或 None（如果请求失败）
        """
        try:
            response = requests.get(url, params=params, timeout=10)  # 设置超时时间
            response.raise_for_status()  # 检查 HTTP 状态码
            return response
        except requests.exceptions.Timeout:
            logging.error(f"Request timed out for TSID {params['TSID']}")
        except requests.exceptions.RequestException as e:
            logging.error(f"Request exception occurred error for TSID {params['TSID']}: {e}")
        return None

            
    def get_mp3_url(self, TSID):
        """
        获取指定 TSID 对应的 MP3 URL
        :param TSID: 歌曲标识符
        :return: MP3 URL 或 None（如果获取失败）
        """
        try:
            # 生成时间戳并计算签名
            timestamp = self._gen_timestamp()
            sign = self._create_sign(TSID) # 因为这里的代码，tsid 可以传入createsign
            
            # 构造请求参数
            params = {
                "appid": self.appid,
                "timestamp": timestamp,
                "sign": sign,
                "TSID": TSID
            }
            
            # 发起请求并处理响应
            response = self._request_tracklink("https://music.91q.com/v1/song/tracklink", params)
            
            # 解析 JSON 数据
            try:
                data = response.json()
            except ValueError:
                logging.error(f"Failed to parse JSON data ")
                return None
            
            # 提取 MP3 URL
            mp3_url = data.get('data', {}).get('path')
            if not mp3_url or not isinstance(mp3_url, str): # mp3_url 为空或类型不为字符串将输出下列报错
                logging.warning(f"No MP3 URL found for TSID: {TSID}; Response Data with no path")
            return mp3_url

        except Exception as e:
            logging.error(f"Unexpected error fetching MP3 URL for TSID {TSID}:\n {e}")
            return None

    def write_to_file(self, mp3_url, filename):
        """
        将 MP3 URL 写入文件
        :param mp3_url: MP3 URL
        :param filename: 文件名
        """
        respose = requests.get(mp3_url, headers=self.headers)
        respose.raise_for_status()
        try:
            with open(filename, 'wb') as file:
                file.write(respose.content)
            logging.info(f"MP3 URL written to {filename}")
        except Exception as e:
            logging.error(f"Failed to download to file: {e}")


if __name__ == "__main__":
    logging.info("MusicScraper is running.")
# 示例调用
    singer = input("请输入歌手名：")
    appid = "16073360"
    secret = "0b50b02fd0d73a9c4c8c3a781c30845f"
    url = f'https://music.91q.com/search?word={singer}'
    scraper = MusicScraper(singer, appid, url, secret)

# 获取歌曲信息并遍历获取 MP3 URL
    music_df = scraper.fetch_music_info()
    if music_df is not None and not music_df.empty:
        for index, row in music_df.iterrows():
            TSID = row['TSID']   # TSID 从 dataframe 中获取
            musicname = row['musicname']
            mp3_url = scraper.get_mp3_url(TSID)  # TSID 传递给 get_mp3_url
            if mp3_url:
                filename = f"{singer}_{musicname}.mp3"
                scraper.write_to_file(mp3_url, filename)
            print(f"歌曲: {row['musicname']}, MP3 URL: {mp3_url} \n")
    else:
        print("未找到歌曲信息。")


'''
下载失败，没解决翻页的问题
'''

